package com.example.springsecuritydemo.config;

import com.example.springsecuritydemo.handler.MyAccessDeniedHandler;
import com.example.springsecuritydemo.handler.MyAuthenticationFailureHandler;
import com.example.springsecuritydemo.handler.MyAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.annotation.Resource;
import javax.sql.DataSource;


/**
 * @Classname: SecurityConfig
 * @Description: SpringSecurity配置类
 * @Date: 2021/3/15 20:44
 * @Created: by Wang Xu
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private MyAccessDeniedHandler myAccessDeniedHandler;

    @Resource
    private UserDetailsService userDetailsService;

    @Resource
    private DataSource dataSource;

    @Resource
    private PersistentTokenRepository persistentTokenRepository;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单登录
        http.formLogin()
                // 自定义登录用户名参数
                .usernameParameter("username123")
                // 自定义登录密码参数
                .passwordParameter("password123")
                // 当发现是/login时候，认为是登录操作，必须和表单提交的地址一样，然后就回去执行UserDetailsServiceImpl自定义登录逻辑
                .loginProcessingUrl("/login")
                // 自定义登录页面
                .loginPage("/toLogin")
                // 认证成功 登录成功后跳转的URL，必须是POST请求
                .successForwardUrl("/toMain")
                //认证成功 自定义登录成功处理器，不能和successForwardUrl共存  比较适合前后端分离项目
//                .successHandler(new MyAuthenticationSuccessHandler("/main.html"))

                // 认证失败，登录失败后跳转的URL，必须是POST请求
                .failureForwardUrl("/toError");
                // 认证失败，自定义登录失败处理器，不能和failureForwardUrl共存     比较适合前后端分离项目
//                .failureHandler(new MyAuthenticationFailureHandler("/error.html"))
        ;

        /*
         ant表达式
         ？ 表示匹配一个字符
         * 表示匹配0个或者多个字符
         ** 表示匹配0个或者多个目录
         */

        // 授权认证 注意是从上到下依次执行----- 相当于拦截器，哪些需要被放行，哪些需要被拦截
        http.authorizeRequests()
                // login.html error.html不需要被认证
                .antMatchers("/error.html").permitAll()
                .antMatchers("/toLogin").permitAll()
//                .antMatchers("/css/**", "/images/**", "/jss/**").permitAll() // 放行静态资源（常用）
//                .antMatchers("/**/*.png").permitAll() // 放行静态资源
//                .regexMatchers(".+[.]png").permitAll() // .表示任意 +表示至少一个 [.]转义  ===》 任何目录下的png结尾的文件  （正则匹配）
//                .regexMatchers(HttpMethod.GET,"/demo").permitAll() // 放行POST请求的 /demo   额外一定要注意的是第一个参数，可以限定请求方式
//                .mvcMatchers("/demo").servletPath("/").permitAll() // 不常用
//                .antMatchers("/server.html").hasAuthority("admin") // 配置一个权限， 表示访问这个资源，必须要有这个权限才可以。 权限严格区分大小写
//                .antMatchers("/server.html").hasAnyAuthority("admin","admin1")
//                .antMatchers("/server.html").hasRole("abc") // 角色这里不能添加前缀 ROLE_   严格区分大小写
//                .antMatchers("/server.html").hasIpAddress("127.0.0.1")
                // 所有请求都必须被认证，必须登录之后才能被访问
                .anyRequest().authenticated();
//                .anyRequest().access("@myServiceImpl.hasPermission(request,authentication)"); // 自定义权限控制

        // 自定义异常处理
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);

        // 记住我
        http.rememberMe()
                // 失效时间 单位秒
                .tokenValiditySeconds(60)
                .rememberMeParameter("remember-me")
                // 自定义登录逻辑
                .userDetailsService(userDetailsService)
                // 持久层对象
                .tokenRepository(persistentTokenRepository);

        // 退出登录
        http.logout()
                .logoutUrl("/logout")
                // 退出登录成功跳转页面
                .logoutSuccessUrl("/login.html");


        // 关闭csrf()防护
//        http.csrf().disable();
    }

    /**
     * @description: 设置加密方式
     * @param: []
     * @return: org.springframework.security.crypto.password.PasswordEncoder
     * @author: wangxu
     * @date: 2021/3/16 22:59
     */
    @Bean
    public PasswordEncoder getPw() {
        return new BCryptPasswordEncoder();
    }

    /**
     * @description: store the persistent login tokens for a user. 
     * @param: [] 
     * @return: org.springframework.security.web.authentication.rememberme.PersistentTokenRepository 
     * @author: wangxu
     * @date: 2021/3/17 23:30
     */
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);

        // 自动建表，第一次启动需要，第二次启动需要注释掉
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
}
